﻿using Godot.core;

namespace Godot.modules.gdscript
{
    public partial class GDFunction
    {
        public enum Opcode
        {
            OPCODE_OPERATOR,
            OPCODE_EXTENDS_TEST,
            OPCODE_SET,
            OPCODE_GET,
            OPCODE_SET_NAMED,
            OPCODE_GET_NAMED,
            OPCODE_ASSIGN,
            OPCODE_ASSIGN_TRUE,
            OPCODE_ASSIGN_FALSE,
            OPCODE_CONSTRUCT, //only for basic types!!
            OPCODE_CONSTRUCT_ARRAY,
            OPCODE_CONSTRUCT_DICTIONARY,
            OPCODE_CALL,
            OPCODE_CALL_RETURN,
            OPCODE_CALL_BUILT_IN,
            OPCODE_CALL_SELF,
            OPCODE_CALL_SELF_BASE,
            OPCODE_JUMP,
            OPCODE_JUMP_IF,
            OPCODE_JUMP_IF_NOT,
            OPCODE_JUMP_TO_DEF_ARGUMENT,
            OPCODE_RETURN,
            OPCODE_ITERATE_BEGIN,
            OPCODE_ITERATE,
            OPCODE_ASSERT,
            OPCODE_LINE,
            OPCODE_END
        };

        public enum Address
        {
            ADDR_BITS = 24,
            ADDR_MASK = ((1 << ADDR_BITS) - 1),
            ADDR_TYPE_MASK = ~ADDR_MASK,
            ADDR_TYPE_SELF = 0,
            ADDR_TYPE_MEMBER = 1,
            ADDR_TYPE_CLASS_CONSTANT = 2,
            ADDR_TYPE_LOCAL_CONSTANT = 3,
            ADDR_TYPE_STACK = 4,
            ADDR_TYPE_STACK_VARIABLE = 5,
            ADDR_TYPE_GLOBAL = 6,
            ADDR_TYPE_NIL = 7
        };

        public struct StackDebug
        {
            public int line;
            public int pos;
            public bool added;
            public StringName identifier;
        };

        private Variant _get_variant(int p_address, GDInstance p_instance, GDScript p_script, Variant r_self, Variant[] p_stack, ref string r_error)
        {
            int address = p_address & ((int)Address.ADDR_MASK);
            switch ((Address)((p_address & ((int)Address.ADDR_TYPE_MASK)) >> ((int)Address.ADDR_BITS)))
            {
                case Address.ADDR_TYPE_SELF: {
                    if (p_instance == null)
                    {
                        r_error = "Cannot access self without instance.";
                        return null;
                    }
                    return r_self;
                } break;

                case Address.ADDR_TYPE_MEMBER: {
                    //member indexing is O(1)
                    if (p_instance == null)
                    {
                        r_error = "Cannot access member without instance.";
                        return null;
                    }
                    return p_instance._members[address];
                } break;

                case Address.ADDR_TYPE_CLASS_CONSTANT: {
                    //todo change to index!
			        GDScript s = p_script;
			        if(address >=_global_names_count)
                        return null;

			        StringName sn = _global_names_ptr[address];

			        while(s != null) 
                    {
                        Variant E = s._constants[sn];
                        if (E != null)
                        {
                            return E;
                        }
                        s = s._base;
			        }

			        return null;
                } break;

                case Address.ADDR_TYPE_LOCAL_CONSTANT: {
                    if(address >= _constant_count)
                        return null;
                    return _constants_ptr[address];
                } break;

                case Address.ADDR_TYPE_STACK:
                case Address.ADDR_TYPE_STACK_VARIABLE: {
                    if(address >= _stack_size) 
                        return null;
                    return p_stack[address];
                } break;

                case Address.ADDR_TYPE_GLOBAL: {
		            if(address >= GDScriptLanguage.singleton.get_global_array().Count)
                        return null;
		            return GDScriptLanguage.singleton.get_global_array()[address];
	            } break;

	            case Address.ADDR_TYPE_NIL: {
		            return new Variant();
	            } break;
            }

            return null;
        }
    }
}
